#if _DEBUG
#pragma comment(linker, "/subsystem:\"console\" /entry:\"WinMainCRTStartup\"")
#endif

#include <SDL.h>
#include <iostream>
#include <glm.hpp>
#include <matrix_transform.hpp>
#include <type_ptr.hpp>
#include <GL/glew.h>
#include <fstream>
#include <vector>
#include <sstream>
using namespace std;

GLuint shaderprogram, flatshader;
GLuint vao,vbo[2];
float r = 0;
vector<GLfloat> verts,cols;
bool key[256];

void sdlFatalErr(char *message)
{
	
	cout << message << " " << SDL_GetError();
	SDL_Quit();
	exit(1);
}

void setupRC(SDL_WindowID &window, SDL_GLContext &context)
{
	if(SDL_Init(SDL_INIT_VIDEO) < 0)
		sdlFatalErr("Unable to initialize SDL");
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);

	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);

	window = SDL_CreateWindow("SDL/GLM/OpenGL Demo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN );

	if (!window)
		sdlFatalErr("Unable to create window");

	context = SDL_GL_CreateContext(window);

	SDL_GL_SetSwapInterval(1);

}

void printShaderInfoLog(GLint shader)
{
	int infoLogLen = 0;
	int charsWritten = 0;
	GLchar *infoLog;

	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
	if(infoLogLen > 0)
	{
		infoLog = new GLchar[infoLogLen];
		glGetShaderInfoLog(shader,infoLogLen, &charsWritten, infoLog);
		cout << "Shader info log: " << endl << infoLog << endl;
		delete [] infoLog;

	}
}

void loadTxt(char *fname, GLint &fSize, vector<GLfloat> &vVector)//, std::vector<GLfloat> &_vVector)
{
	ifstream file(fname);

	if(file.is_open())
	{
		string temp,coord;
		while(file.good())
		{
			getline(file, temp);	
		}
		stringstream ss(temp);

		while (ss >> coord)
		{
			vVector.push_back(atof(coord.c_str()));
		}
		fSize = (GLint)vVector.size();
		file.close();
	}else
		cout << "Unable to open file " << endl;
}

char* loadFile(char *fname, GLint &fSize)
{
	int size;
	char * memblock;
	string text;

	// file read based on example in cplusplus.com tutorial
	ifstream file (fname, ios::in|ios::binary|ios::ate);
	if (file.is_open())
	{
		size = (int)file.tellg();
		fSize = (GLuint) size;
		memblock = new char [size+1];
		file.seekg (0, ios::beg);
		file.read (memblock, size);
		file.close();
		memblock[size] = 0;
		cout << "file " << fname << " loaded" << endl;
		text.assign(memblock);
	}
	else
	{
		cout << "Unable to open file " << fname << endl;
		exit(1);
	}
	return memblock;
}

GLuint initShaders(char *vertex_shader, char *fragment_shader)
{
	GLuint p, f, v;// handles for shader program & vertex and fragment shaders
	char *vs,*fs;  // buffers for shader programs

	// create vertex and fragment shader handles
	v = glCreateShader(GL_VERTEX_SHADER);
	f = glCreateShader(GL_FRAGMENT_SHADER);	

	GLint vss,fss;

	// load shaders into buffers
	vs = loadFile(vertex_shader,vss);
	fs = loadFile(fragment_shader,fss);
	
	const char * vv = vs;
	const char * ff = fs;

	// send the shader source to OpenGL
	// strings here are null terminated - if final param is non-zero that would
	// indicate length of the shader source instead
	glShaderSource(v, 1, &vv,0);
	glShaderSource(f, 1, &ff,0);
	
	GLint compiled, linked; // vals for checking for compile & link errors


	//Compile vertex shader
	glCompileShader(v);
	glGetShaderiv(v, GL_COMPILE_STATUS, &compiled);
	if(!compiled)
	{
		cout << "Vertex shader not compiled" << endl;
		printShaderInfoLog(v);
	}

	//Compile fragment shader
	glCompileShader(f);
	glGetShaderiv(f,GL_COMPILE_STATUS, &compiled);
	if(!compiled)
	{
		cout << "Fragment shader not compiled" << endl;
		printShaderInfoLog(f);
	}

	p= glCreateProgram();
	glAttachShader(p,v);
	glAttachShader(p,f);

	glBindAttribLocation(p,0,"in_Position");
	glBindAttribLocation(p,1,"in_Color");
	glLinkProgram(p);
	glGetProgramiv(p, GL_LINK_STATUS, &linked);

	if(!linked)
	{
		int infoLogLen = 0;
		int charsWritten = 0;
		GLchar *infoLog;
		glGetProgramiv(p,GL_INFO_LOG_LENGTH, &infoLogLen);

		infoLog = (char *)malloc(infoLogLen);
		glGetProgramInfoLog(p, infoLogLen, &infoLogLen, infoLog);
		cout << "Shader program failed to link" << endl;
		printShaderInfoLog(*infoLog);
		delete [] infoLog;
	}
	
	glUseProgram(p);

	delete [] vs;
	delete [] fs;

	return p;
}

/*GLuint initShaders(void)
{
	GLuint p, f, v;// handles for shader program & vertex and fragment shaders
	char *vs,*fs;  // buffers for shader programs

	// create vertex and fragment shader handles
	v = glCreateShader(GL_VERTEX_SHADER);
	f = glCreateShader(GL_FRAGMENT_SHADER);	

	GLint vss,fss;

	// load shaders into buffers
	vs = loadFile("simple.vert",vss);
	fs = loadFile("simple.frag",fss);
	
	const char * vv = vs;
	const char * ff = fs;

	// send the shader source to OpenGL
	// strings here are null terminated - if final param is non-zero that would
	// indicate length of the shader source instead
	glShaderSource(v, 1, &vv,0);
	glShaderSource(f, 1, &ff,0);
	
	GLint compiled, linked; // vals for checking for compile & link errors


	//Compile vertex shader
	glCompileShader(v);
	glGetShaderiv(v, GL_COMPILE_STATUS, &compiled);
	if(!compiled)
	{
		cout << "Vertex shader not compiled" << endl;
		printShaderInfoLog(v);
	}

	//Compile fragment shader
	glCompileShader(f);
	glGetShaderiv(f,GL_COMPILE_STATUS, &compiled);
	if(!compiled)
	{
		cout << "Fragment shader not compiled" << endl;
		printShaderInfoLog(f);
	}

	p= glCreateProgram();
	glAttachShader(p,v);
	glAttachShader(p,f);

	glBindAttribLocation(p,0,"in_Position");
	glBindAttribLocation(p,1,"in_Color");
	glLinkProgram(p);
	glGetProgramiv(p, GL_LINK_STATUS, &linked);

	if(!linked)
	{
		int infoLogLen = 0;
		int charsWritten = 0;
		GLchar *infoLog;
		glGetProgramiv(p,GL_INFO_LOG_LENGTH, &infoLogLen);

		infoLog = (char *)malloc(infoLogLen);
		glGetProgramInfoLog(p, infoLogLen, &infoLogLen, infoLog);
		cout << "Shader program failed to link" << endl;
		printShaderInfoLog(*infoLog);
		delete [] infoLog;
	}
	
	glUseProgram(p);

	delete [] vs;
	delete [] fs;

	return p;
}*/

void init(void)
{
	
	for(unsigned int i = 0; i<256;i++)
	{
		key[i] = false;
	}



	GLint vertsize,colsize;
	loadTxt("verts.txt",vertsize,verts);
	loadTxt("cols.txt", colsize, cols);
	
	//Initialise shaders
	shaderprogram = initShaders("simple.vert", "simple.frag"); // create and start shader program
	flatshader = initShaders("flat_shader.vert", "flat_shader.frag");

	glGenVertexArrays(1, &vao); // allocate & assign a Vertex Array Object (VAO)
    glBindVertexArray(vao); // bind VAO as current object

    glGenBuffers(2, vbo); // allocate two Vertex Buffer Objects (VBO)
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // bind 1st VBO as active buffer object
 
    // Copy the vertex data from diamond to our VBO
    // 15 * sizeof(GLfloat) is the size of the pyramid array,
    // since it contains 15 GLfloat values 
    glBufferData(GL_ARRAY_BUFFER, vertsize * sizeof(GLfloat), &(verts[0]), GL_STATIC_DRAW);
 
	// Specify that position data is going into attribute index 0, and 
// contains three floats per vertex
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);     // Enable attribute index 0 as being used
 
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); // bind 2nd VBO as active buf obj
 
    // Copy the color data from colors to our buffer
    // 15 * sizeof(GLfloat) is the size of the colors array, since it 
    // contains 15 GLfloat values
    glBufferData(GL_ARRAY_BUFFER, colsize * sizeof(GLfloat), &(cols[0]), GL_STATIC_DRAW);
 
    // Specify that our color data is going into attribute index 1, and 
    // contains three floats per vertex 
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(1);    // Enable attribute index 1 as being used

// OpenGL is state based - we normally need to keep references to shader, VAO, 
// VBO, etc., to allow swapping between different ones. In this program there 
// is only one shader program and one VAO

   glEnable(GL_DEPTH_TEST); // enable depth testing
}


//void Draw(SDL_WindowID &window)
//{
//	glClear(GL_COLOR_BUFFER_BIT);
//	glClearColor(1.0f,0.0f,0.0f,0.0f);
//
//
//	SDL_GL_SwapWindow(window); //Swap Buffers
//	
//}

void draw(const SDL_WindowID &window)
{
	glClearColor(1.0, 1.0, 1.0, 1.0); // set background colour
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear window

	// Create perspective projection matrix
	glm::mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 1.0f, 100.f);

	// Apply model view transformations
	glm::mat4 identity(1.0); 
	glm::mat4 modelview = glm::translate( identity, glm::vec3(0.0f, 0.0f, -7.0f));
	modelview = glm::translate(modelview,glm::vec3(1.0f, 0.0f, 1.0f));
	modelview = glm::rotate( modelview, r, glm::vec3(0.0f,1.0f,0.0f));
	modelview = glm::translate(modelview,glm::vec3(-1.0f, 0.0f, -1.0f));
	glm::mat4 MVP = projection * modelview;  // Calculate final MVP matrix

	// pass MVP as uniform into shader
	glUseProgram(shaderprogram);

	int mvpIndex = glGetUniformLocation(shaderprogram, "MVP");
	glUniformMatrix4fv(mvpIndex, 1, GL_FALSE, glm::value_ptr(MVP)); 
	glDrawArrays(GL_TRIANGLE_FAN, 0, verts.size()/3); // draw the pyramid

	glUseProgram(flatshader);
	mvpIndex = glGetUniformLocation(flatshader, "MVP");
	modelview = glm::translate(identity,glm::vec3(-1.0f, 0.0f, -5.0f));
	//modelview = glm::mat4(1.0); Set it to identity
	MVP = projection * modelview;  // Calculate final MVP matrix
	glUniformMatrix4fv(mvpIndex, 1, GL_FALSE, glm::value_ptr(MVP)); 
	glDrawArrays(GL_TRIANGLE_FAN, 0, verts.size()/3); // draw the pyramid

	//r+=0.5;

	SDL_GL_SwapWindow(window); // swap buffers
}


void cleanup(void)
{
    glUseProgram(0);
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
	// could also detach shaders
    glDeleteProgram(shaderprogram);
	glDeleteProgram(flatshader);
    glDeleteBuffers(2, vbo);
    glDeleteVertexArrays(1, &vao);
}

void keyDown(SDL_Event &sdlEvent)
{
	//if(sdlEvent.type = SDL_KEYDOWN)
		key[sdlEvent.key.keysym.sym]=true; // Need a check to see if it's a key with a ascii value being pressed, else the program will break when i.e shift is pressed.
}
void keyUp(SDL_Event &sdlEvent)
{
	//if(sdlEvent.type = SDL_KEYUP)
		key[(int)sdlEvent.key.keysym.sym]=false; // Need a check to see if it's a key with a ascii value being pressed, else the program will break when i.e shift is pressed.
}

void update(SDL_Event &sdlEvent)
{
	if(key[SDLK_w])
		r+=0.5;
	if(key[SDLK_s])
		r-=0.5;
}

int main(int argc, char *argv[])
{
	SDL_WindowID hWindow; //Window handle
	SDL_GLContext glContext;
	setupRC(hWindow,glContext);
	GLenum err = glewInit();
	if(GLEW_OK != err)
	{
		cout << "glewinit failed, aborting. " << endl;
		exit(1);
	}

	SDL_Event sdlEvent;
	std::cout << "Hello World" << endl;
	bool running = true;
	init();
	while (running)
	{
		
		while(SDL_PollEvent(&sdlEvent))
		{
			
			if(sdlEvent.type == SDL_QUIT)
				running = false;
			switch(sdlEvent.type)
			{
			case SDL_KEYDOWN:
				keyDown(sdlEvent);
				break;
			case SDL_KEYUP:
				keyUp(sdlEvent);
				break;
			}
		}
		//iohandling(sdlEvent);
		update(sdlEvent);
		draw(hWindow);
	}
	cleanup();
	SDL_GL_DeleteContext(glContext);
	SDL_DestroyWindow(hWindow);
	SDL_Quit();
	return 0;
}
